package api

import (
	"ChdChat-Gin/initialize"
	"ChdChat-Gin/middleware"
	"ChdChat-Gin/model"
	"ChdChat-Gin/service"
	"ChdChat-Gin/utils"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gomodule/redigo/redis"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"strconv"
	"time"
)

func ConnectChatRoom(c *gin.Context) {
	// 解析子协议，获取 Token
	tokenStr := c.GetHeader("Sec-WebSocket-Protocol")
	claims, err := authenticate(tokenStr)
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{
			"msg": "Authentication failed",
		})
		log.Printf("ConnectChatRoom.authenticate:%v\n", err)
		return
	}
	wsConn, err2 := (&websocket.Upgrader{
		Subprotocols: []string{tokenStr},
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}).Upgrade(c.Writer, c.Request, nil)
	if err2 != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": "Failed to connect to chat room",
		})
		fmt.Printf("ConnectChatRoom.Upgrade:%v\n", err2)
		return
	}
	// generating chat room client
	client := &service.Client{
		Type:       claims.Type,
		Name:       claims.Name,
		ID:         claims.Account,
		Socket:     wsConn,
		Start:      time.Now(),
		IpAddress:  c.ClientIP(),
		ExpireTime: time.Minute * 60,
		Sender:     make(chan model.ChatRoomMessage, 64),
		Receiver:   make(chan model.ChatRoomMessage, 64),
	}
	service.ChatRoomManager.Login <- client
}

func authenticate(tokenStr string) (*model.Claims, error) {
	if tokenStr == "" {
		return nil, errors.New("token is empty")
	}
	claims, err := utils.ParseToken(tokenStr)
	if err != nil {
		log.Printf("utils.ParseToken:%v", err)
		return nil, errors.New("failed to parse token")
	}
	redisConn := initialize.RedisPool.Get()
	defer func(redisConn redis.Conn) {
		if err := redisConn.Close(); err != nil {
			panic(err)
		}
	}(redisConn)
	// 从redis中获取认证信息
	marshaled, err := redis.String(redisConn.Do("GET", claims.RdsKey))
	if err != nil {
		log.Printf("redis.String:%v\n", err)
		// 查找不到的原因：未登录、rdsKey被修改、认证信息过期
		return nil, middleware.ErrNotAuth
	}
	// 解析认证信息为json格式
	rdsVal := struct {
		Encoded  string          `json:"encoded"`
		ClientIp string          `json:"clientIp"`
		UserInfo model.UserInfos `json:"userInfo"`
	}{}
	if err := json.Unmarshal([]byte(marshaled), &rdsVal); err != nil {
		log.Printf("json.Unmarshal:%v\n", err)
		return nil, middleware.ErrDecrypt
	}
	// Base64解码密文
	cipherBytes, err := base64.StdEncoding.DecodeString(rdsVal.Encoded)
	if err != nil {
		log.Printf("base64.DcodeString:%v\n", err)
		return nil, middleware.ErrDecrypt
	}
	// 创建AES解密器
	aesKey, err := hex.DecodeString(claims.AesKey)
	if err != nil {
		log.Printf("hex.DecodeString:%v\n", err)
		return nil, middleware.ErrDecrypt
	}
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		log.Printf("aes.NewCipher:%v\n", err)
		return nil, middleware.ErrDecrypt
	}
	// 解密
	iv := cipherBytes[:aes.BlockSize]
	cipherText := cipherBytes[aes.BlockSize:]
	blockMode := cipher.NewCBCDecrypter(block, iv)
	plaintext := make([]byte, len(cipherText))
	blockMode.CryptBlocks(plaintext, cipherText)
	// 去除填充字符
	padding := int(plaintext[len(plaintext)-1])
	plaintext = plaintext[:len(plaintext)-padding]

	if string(plaintext) != claims.Account {
		log.Println("Authentication illegal")
		return nil, middleware.ErrIllegalAuthInfo
	}
	return claims, nil
}

func FetchChatHistory(c *gin.Context) {
	claims := c.MustGet("claims").(*model.Claims)

	End, _ := strconv.Atoi(c.Query("end"))
	Start, _ := strconv.Atoi(c.Query("start"))
	chatHistory, err := service.FetchChatHistory(claims.Account, Start, End)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": "Failed to fetch chat history",
		})
		log.Printf("FetchChatHistory.FetchChatHistory:%v\n", err)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"msg":  "Fetch chat history successfully",
		"data": chatHistory,
	})
}

func DeleteChatHistory(c *gin.Context) {
	claims := c.MustGet("claims").(*model.Claims)
	var data struct {
		Account string `json:"account"`
	}
	if err := c.BindJSON(&data); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"msg": fmt.Sprintf("Parameter format error, %v", err),
		})
		log.Printf("DeleteChatHistory.BindJSON:%v\n", err)
		return
	}
	result, err := service.DeleteChatHistory(claims.Account, data.Account)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": "Failed to delete chat history",
		})
		log.Printf("DeleteChatHistory.DeleteChatHistory:%v\n", err)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"msg":  "Delete chat history successfully",
		"data": result,
	})
}

func QuitChatRoom(c *gin.Context) {
	claims := c.MustGet("claims").(*model.Claims)
	if err := service.QuitChatRoom(claims.Account); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": "Failed to disconnect chat room",
		})
		log.Printf("QuitChatRoom.QuitChatRoom:%v\n", err)
		return
	}
}

func CreateRoom(c *gin.Context) {
	claims := c.MustGet("claims").(*model.Claims)
	var data struct {
		Password string `json:"password"`
	}
	if err := c.BindJSON(&data); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"msg": fmt.Sprintf("Parameter format error, %v", err),
		})
		log.Printf("CreateRoom.BindJSON:%v\n", err)
		return
	}
	if err := service.GameRoomManager.CreateRoom(claims.Account, data.Password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": "Failed to create a game room",
		})
		log.Printf("CreateRoom.CreateRoom:%v\n", err)
		return
	}
}

func JoinRoom(c *gin.Context) {
	claims := c.MustGet("claims").(*model.Claims)
	var data struct {
		Id       int    `json:"id"`
		Password string `json:"password"`
	}
	if err := c.BindJSON(&data); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"msg": fmt.Sprintf("Parameter format error, %v", err),
		})
		log.Printf("JoinRoom.BindJSON:%v\n", err)
		return
	}
	if err := service.GameRoomManager.JoinRoom(data.Id, claims.Account, data.Password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": "Failed to Join room",
		})
		log.Printf("JoinRoom.JoinRoom:%v\n", err)
		return
	}
}

func NewGame(c *gin.Context) {
	var data struct {
		Id int `json:"id"`
	}
	if err := c.BindJSON(&data); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"msg": fmt.Sprintf("Parameter format error, %v", err),
		})
		log.Printf("JoinRoom.BindJSON:%v\n", err)
		return
	}
	if err := service.GameRoomManager.NewGame(data.Id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": "Failed to start a new game",
		})
		log.Printf("NewGame.NewGame:%v\n", err)
		return
	}
}
